查看原文
其他

.NET Core 中延迟单例另一种写法

DotNet 2019-08-03

(给DotNet加星标,提升.Net技能


转自:莫问今朝·

cnblogs.com/yan7/p/9187874.html


一、BeforeFieldInit是什么


在反编译代码时无意间看到在类中有一个BeforeFieldInit特性,处于好奇的心态查了查这个特性,发现这是一个关于字段初始化时间的特性【提前初始化字段】,下面先来看一下这个特性在.NET Framework中的作用。


class Foo
{
  public static String x = GetStr("初始化 Foo 静态成员字段");
  public static String GetStr(String str)
 
{
      Console.WriteLine(str);
      return str;
 }
}


在上面Foo类中只定义了一个静态字段x和一个静态方法GetStr的方法,在这里需要关注的是静态字段x的初始化时机。


static void Main(string[] args)
{
     Console.WriteLine("Main方法开始");
     Foo.GetStr("手动调用Foo.GetSring()方法");
     String y = Foo.x;
}


在Main中简单的调用静态方法和静态字段,我们知道静态字段的赋值是在静态构造函数中进行的,那么输出顺序应该是 “Main方法开始”,”初始化Foo静态成员字段“,”手动调用Foo.GetString()方法“,但是真的是这样吗,答案是错的。



可以看到静态成员字段的初始化是在最开始,那么为什么会这样呢,我们将代码反编译IL后会发现在类中具有一个beforefieldinit特性。


.class private auto ansi beforefieldinit BeoreFieldInitTest2.Foo
   extends [mscorlib]System.Object
{
} // end of class BeoreFieldInitTest2.Foo


那么BeforeFieldInit是什么,我找到了一篇文章(http://csharpindepth.com/Articles/General/Beforefieldinit.aspx)有对BeforeFieldInit的详细讲解,在这里也不过多介绍。


二、取消BeforeFieldInit加载


那么该怎么取消beforefieldinit特性呢,其实很简单,只需要在类中加入一个静态构造函数即可。


class Foo
{
    public static string x = GetStr("初始化 Foo 静态成员字段");
    //空的静态构造函数
    static Foo(){}
    public static String GetStr(String str)
   
{
        Console.WriteLine(str);
         return str;
     }
}


然后此时输入就如我们所猜测那样。



并且反编译可以看到IL代码也取消了beforefieldinit特性。


.class private auto ansi BeoreFieldInitTest2.Foo
   extends [mscorlib]System.Object
{
} // end of class BeoreFieldInitTest2.Foo


下面就该进入正题,来看看.NET Core中不一样的BeforeFieldInit。


三、BeforeFieldInit在.NET Core 中的差异


将最开始的代码在.NET Core中跑一跑会发现跟.NET Framework不一样的操作。


class Program
{
   static void Main(string[] args)
   
{
       Console.WriteLine("Main方法开始");
       Foo.GetStr("手动调用Foo.GetSring()方法");
       String y = Foo.x;
   }
}
class Foo
{
   public static string x = GetStr("初始化 Foo 静态成员字段");
   public static String GetStr(String str)
   
{
       Console.WriteLine(str);
       return str;
   }
}



可以看到在.NET Core并没有像.NET Framework那样进行提前加载,并且加载貌似还延迟了,反编译代码可以看到beforefieldinit特性还在Foo类上。


.class private auto ansi beforefieldinit BeforeFieldInitTest.Foo
   extends [System.Runtime]System.Object
{
} // end of class BeforeFieldInitTest.Foo


那么在.NET Core加入静态构造函数会怎么呢?怀着各种疑惑进行测试。


class Program
{
    static void Main(string[] args)
   
{
         Console.WriteLine("Main方法开始");
         Foo.GetStr("手动调用Foo.GetSring()方法");
         String y = Foo.x;
     }
}
class Foo
{
     public static string x = GetStr("初始化 Foo 静态成员字段");
     //空的静态构造函数
     static Foo() { }
     public static String GetStr(String str)
     
{
         Console.WriteLine(str);
         return str;
     }
}



可以看到.NET Core中加入静态构造函数以后输出跟.NET Framework一致,也就说可以猜测.NET Core运行时对beforefieldinit特性进行了优化,当然这也只是我的猜测。


四、利用.NET Core中beforefieldinit实现的单例


在.NET Framework中我们都是使用Lazy<>类来创建延迟加载单例,但是我们可以看到在.NET Core中beforefieldinit是延迟加载的,所以我们直接可以使用此方法来创建延迟安全单例。


class Program
{
   static void Main(string[] args)
   
{
        Console.WriteLine("Main方法开始");
        Foo.GetStr("手动调用Foo.GetSring()方法");
        Console.WriteLine("我是分隔符");
        Console.WriteLine("我是分隔符");
        var foo= Foo.CreateInstance;
    }
}
class Foo
{
    public static Foo CreateInstance { get;  } = new Foo();
    private Foo()
   
{
        Console.WriteLine("创建了Foo实例");
    }
    public static String GetStr(String str)
   
{
        Console.WriteLine(str);
        return str;
    }
}


运行结果可以看到创建实例被延迟了。



当然,这种创建单例也是有缺点的,当类中还有其它静态字段或属性时,并且在外部进行了调用,那么此时也会初始化此属性。


class Program
{
    static void Main(string[] args)
   
{
        Console.WriteLine("Main方法开始");
        Foo.GetStr("手动调用Foo.GetSring()方法");
        var y = Foo.x;//调用静态字段/属性
        Console.WriteLine("我是分隔符");
        Console.WriteLine("我是分隔符");
        var foo= Foo.CreateInstance;
    }
}
class Foo
{
    public static string x = GetStr("初始化 Foo 静态成员字段"); //加入了静态字段或属性
    //public static String X { get; set; } = GetStr("初始化 Foo 静态成员字段");
    public static Foo CreateInstance { get;  } = new Foo();
    private Foo()
   
{
        Console.WriteLine("创建了Foo实例");
    }
    public static String GetStr(String str)
   
{
        Console.WriteLine(str);
        return str;
    }
}



也就是说在.NET Core中beforfieldinit特性时当有一个静态变量被使用时就初始化所有静态变量。


推荐阅读

(点击标题可跳转阅读)

ASP.NET Core 中间件

CAP带你轻松玩转ASP.NET Core消息队列

.NET Standard FreeSql v0.0.9功能预览


看完本文有收获?请转发分享给更多人

关注「DotNet」加星标,提升.Net技能 

喜欢就点一下「好看」呗~

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存